#include "declaration.h"
#include "syntax.h"
#include "token.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int internal_counter = 0;

int declaration( lex_info *lex )
{
    token *tk;
    vartype *vt;
    vt = vt_create( T_SIMPLE, NULL, 0 );
    if( declaration_specifiers( lex, vt ) == 0 )
    {
        init_declarator_list( lex, vt );
        tk = (token *)lt_get( lex->tokens, lex->cur_token );
        
        if( *tk->name == ';' )
            ++ lex->cur_token;
        vt_release( vt );
        return 0;
    }
    return -1;
}

int declaration_specifiers( lex_info *lex, vartype *vt )
{
    token *tk;
    symbol *sym;
    tk = (token *)lt_get( lex->tokens, lex->cur_token );
    if( TOKEN_TYPE(tk) == KEYWORD )
    {
        switch( KW_TYPE(tk) )
        {
        case KW_STRUCT:
        case KW_UNION:
            sym = struct_or_union_specifier( lex );
            if( sym && vt->name == NULL )
                vt->name = sym;
            else if( sym )
                sym_release( sym );
            else
                goto EORROR_PROC;
            break;
        case KW_ENUM:
            sym = enum_specifier( lex );
            if( sym && vt->name == NULL )
                vt->name = sym;
            else if( sym )
                sym_release( sym );
            else
                goto EORROR_PROC;
            break;
        case KW_TYPEDEF:
            vt->ext |= VTE_TYPEDEF;
            break;
        case KW_EXTERN:
            vt->ext |= VTE_EXTERN;
            break;
        case KW_STATIC:
            vt->ext |= VTE_STATIC;
            break;
        case KW_CONST:
            vt->ext |= VTE_CONST;
            break;
        case KW_INLINE:
            vt->ext |= VTE_INLINE;
            break;
        case KW_AUTO:
            vt->ext |= VTE_AUTO;
            break;
        case KW_REGISTER:
            vt->ext |= VTE_REGISTER;
            break;
        case KW_RESTRICT:
            vt->ext |= VTE_RESTRICT;
            break;
        case KW_VOLATILE:
            vt->ext |= VTE_VOLATILE;
            break;
        default:
            sym = sym_find( tk->name );
            if( sym && sym_is_type( sym ) && vt->name == NULL )
                vt->name = sym;
            else if( sym )
                sym_release( sym );
            break;
        }
        declaration_specifiers( lex, vt );
        return 0;
    }
    else if( TOKEN_TYPE(tk) == IDENTIFIER )
    {
        sym = sym_find( tk->name );
        if( sym && sym_is_type( sym ) && vt->name == NULL )
            vt->name = sym;
        else if( sym )
            sym_release( sym );
        return 0;
    }
EORROR_PROC:
    return -1;
}

int         init_declarator_list( lex_info *lex, vartype *ty );
int         init_declarator( lex_info *lex, vartype *ty );

symbol* struct_or_union_specifier( lex_info *lex )
{
    token *tk;
    structtype *st;
    symtype *s;
    syntype t;
    symbol *sym;
    int i;
    st = (structtype *)malloc( sizeof(structtype) );
    tk = (token *)lt_get( lex->tokens, lex->cur_token );
    switch( KW_TYPE(tk) )
    {
    case KW_STRUCT:
        t = T_STRUCT;
        break;
    case KW_UNION:
        t = T_UNION;
        break;
    default:
        goto ERROR_PROC;
    }
    s = symtype_create( t, st, 0 );

    ++lex->cur_token;
    tk = (token *)lt_get( lex->tokens, lex->cur_token );
    if( TOKEN_TYPE(tk) == IDENTIFIER )
    {
        st->name = tk;
        add_ref( tk );
        sym = sym_create( s, st->name, (symbol *)lt_back( lex->region ) );
        ++lex->cur_token;
        tk = (token *)lt_get( lex->tokens, lex->cur_token );
        if( *tk->name == '{' )
            ++lex->cur_token;
        lt_pushb( lex->region, sym );
        st->member = struct_declaration_list( lex );
        lt_popb( lex->region );
        tk = (token *)lt_get( lex->tokens, lex->cur_token );
        if( *tk->name == '}' )
            ++lex->cur_token;
    }
    else if( *tk->name == '{' )
    {
        char buf[1024];
        sprintf( buf, "unamed_struct_%d", internal_counter++ );
        st->name = token_create_raw( IDENTIFIER, buf, "__internal_file__", -1, NULL, NULL );
        sym = sym_create( s, st->name, (symbol *)lt_back( lex->region ) );
        ++lex->cur_token;
        lt_pushb( lex->region, sym );
        st->member = struct_declaration_list( lex );
        lt_popb( lex->region );
        tk = (token *)lt_get( lex->tokens, lex->cur_token );
        if( *tk->name == '}' )
            ++lex->cur_token;
    }
    else
    {
        goto ERROR_PROC;
    }
    for( i = 0, s->size = 0; i < lt_size( st->member ); ++i )
    {
        sym = (symbol *)lt_get( st->member, i );
        s->size += sym_size( sym );
    }
    return sym;
ERROR_PROC:
    free( st );
    return NULL;
}

list* struct_declaration_list( lex_info *lex )
{
    list *lt, *l;
    l = struct_declaration( lex );
    lt = l;
    while( l )
    {
        l = struct_declaration( lex );
        if( l )
        {
            lt_merge( lt, l );
            lt_destroy( l );
        }
    }
    return lt;
}

list* struct_declaration( lex_info *lex )
{
    vartype *vt;
    list *lt;
    vt = specifier_qualifier_list( lex );
    if( vt )
    {
        lt = struct_declarator_list( lex, vt );
        vt_release( vt );
    }
    return lt;
}

vartype* specifier_qualifier_list( lex_info *lex )
{
    symbol *sym;
    token *tk;
    vartype *vt;
    vt = vt_create( T_SIMPLE, NULL, 0 );
    tk = (token *)lt_get( lex->tokens, lex->cur_token );
    if( TOKEN_TYPE(tk) == KEYWORD )
    {
        switch( KW_TYPE(tk) )
        {
        case KW_CONST:
            vt->ext |= VTE_CONST;
            break;
        case KW_RESTRICT:
            vt->ext |= VTE_RESTRICT;
            break;
        case KW_VOLATILE:
            vt->ext |= VTE_VOLATILE;
            break;
        case KW_STRUCT:
        case KW_UNION:
            sym = struct_or_union_specifier( lex );
            if( sym && vt->name == NULL )
                vt->name = sym;
            else if( sym )
                sym_release( sym );
            else
                goto ERROR_PROC;
            break;
        case KW_ENUM:
            sym = enum_specifier( lex );
            if( sym && vt->name == NULL )
                vt->name = sym;
            else if( sym )
                sym_release( sym );
            else
                goto ERROR_PROC;
            break;
        default:
            sym = sym_find( tk->name );
            if( sym && sym_is_type( sym ) && vt->name == NULL )
                vt->name = sym;
            else if( sym )
                sym_release( sym );
            else
                goto ERROR_PROC;
            break;
        }
    }
    else if( TOKEN_TYPE(tk) == IDENTIFIER )
    {
        sym = sym_find( tk->name );
        if( sym && sym_is_type( sym ) && vt->name == NULL )
            vt->name = sym;
        else if( sym )
            sym_release( sym );
        else
            goto ERROR_PROC;
    }
    return vt;
ERROR_PROC:
    vt_release( vt );
    return NULL;
}

list* struct_declarator_list( lex_info *lex, vartype *vt )
{
    token *tk;
    symbol *sym;
    exp_tree *exp;
    list *lt;
    lt = lt_create();
    do 
    {
        sym = struct_declarator( lex, vt );
        if( sym )
        {
            lt_pushb( lt, sym );
            tk = (token*)lt_get( lex->tokens, lex->cur_token );
            if( *tk->name == ',' )
            {
                ++lex->cur_token;
                exp = exp_const( lex );
            }
        }
    } while ( sym );
    return lt;
}

symbol* struct_declarator( lex_info *lex, vartype *vt )
{
    exp_tree *exp;
    token *tk;
    symbol *sym;
    sym = declarator( lex, vt );
    tk = (token *)lt_get( lex->tokens, lex->cur_token );
    if( *tk->name == ':' )
    {
        ++lex->cur_token;
        exp = exp_const( lex );
        exp_tree_release( exp );
    }
    return sym;
}

symbol* enum_specifier( lex_info *lex )
{
    symbol *sym = NULL;
    enumtype *et = NULL;
    token *tk;
    tk = (token *)lt_get( lex->tokens, lex->cur_token );
    if( KW_TYPE(tk) == KW_ENUM )
    {
        ++lex->cur_token;
        tk = (token *)lt_get( lex->tokens, lex->cur_token );
        if( TOKEN_TYPE(tk) == IDENTIFIER )
        {
            et = (enumtype *)malloc( sizeof(enumtype) );
            et->name = tk;
            add_ref( tk );
        }
        if( *tk->name == '{' )
        {
            if( et == NULL )
            {
                token *t;
                char buf[1024];
                sprintf( buf, "enum_%d", internal_counter );
                t = token_create_raw( IDENTIFIER, buf, "__internal_file__", -1, NULL, NULL );
                et = (enumtype *)malloc( sizeof(enumtype) );
                et->name = t;
            }
            ++lex->cur_token;
            et->member = enumerator_list( lex );
            tk = (token *)lt_get( lex->tokens, lex->cur_token );
            if( *tk->name == ',' )
            {
                ++lex->cur_token;
                tk = (token *)lt_get( lex->tokens, lex->cur_token );
            }
            if( *tk->name == '}' )
                ++lex->cur_token;
        }
        if( et )
        {
            symtype *st = symtype_create( T_ENUM, et, 4 );
            symbol *r = (symbol *)lt_back( lex->region );
            sym = sym_create( st, et->name, r );
            symtype_release( st );
        }
    }
    return sym;
}

list* enumerator_list( lex_info *lex )
{
    symbol *sym;
    token *tk;
    list *lt;
    lt = lt_create();
    do 
    {
        sym = enumerator( lex );
        tk = (token *)lt_get( lex->tokens, lex->cur_token );
        if( *tk->name == ',' )
            ++lex->cur_token;
        if( sym )
            lt_pushb( lt, sym );
    } while ( sym );
    return lt;
}

symbol* enumerator( lex_info *lex )
{
    symbol *sym = NULL;
    token *tk;
    tk = (token *)lt_get( lex->tokens, lex->cur_token );
    if( TOKEN_TYPE(tk) == IDENTIFIER )
    {
        symbol *r = (symbol *)lt_back( lex->region );
        exp_tree *exp = NULL;
        token *t;
        symtype *st;
        ++lex->cur_token;
        t = (token *)lt_get( lex->tokens, lex->cur_token );
        if( *t->name == '=' )
        {
            ++lex->cur_token;
            exp = exp_const( lex );
        }
        st = symtype_create( T_CONST, exp, 4 );
        sym = sym_create( st, tk, r );
        symtype_release( st );
        if( exp )
            exp_tree_release( exp );
    }
    return sym;
}

vartype* type_name( lex_info *lex )
{
    vartype *vt;
    vt = specifier_qualifier_list( lex );
    abstract_declarator( lex, vt );
    return vt;
}

vartype*    abstract_declarator( lex_info *lex, vartype *ty )
{
    pointer( lex, ty );
    return direct_abstract_declarator( lex, ty );
}

vartype*    direct_abstract_declarator( lex_info *lex, vartype *ty );
symbol*     declarator( lex_info *lex, vartype *ty );
symbol*     direct_declarator( lex_info *lex, vartype *ty );
vartype*    pointer( lex_info *lex, vartype *ty );
vartype*    type_qualifier_list( lex_info *lex, vartype *ty );
list*       parameter_type_list( lex_info *lex );
list*       parameter_list( lex_info *lex );
vartype*    parameter_declaration( lex_info *lex );
list*       identifier_list( lex_info *lex );
int         initializer( lex_info *lex );
int         initializer_list( lex_info *lex );
int         designation( lex_info *lex );
int         designator_list( lex_info *lex );
int         designator( lex_info *lex );
